package com.demo2do.fms.admin.service.business.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import com.demo2do.core.entity.exception.BusinessException;
import com.demo2do.core.entity.page.Page;
import com.demo2do.core.persistence.GenericDaoSupport;
import com.demo2do.core.persistence.annotation.Dao;
import com.demo2do.core.persistence.support.QueryBuilder;
import com.demo2do.core.utils.DateFormatUtils;
import com.demo2do.fms.admin.dao.business.FinanceDao;
import com.demo2do.fms.admin.dao.business.OutshipmentDao;
import com.demo2do.fms.admin.dao.business.RemoteMessageDaoSupport;
import com.demo2do.fms.admin.dao.business.SequenceDaoSupport;
import com.demo2do.fms.admin.dao.business.ShipmentDao;
import com.demo2do.fms.admin.dao.supplier.OrganizationDao;
import com.demo2do.fms.admin.dto.LoadComponent;
import com.demo2do.fms.admin.entity.report.business.ConsignorShipmentReportSearch;
import com.demo2do.fms.admin.entity.search.business.ConsignorShipmentSearch;
import com.demo2do.fms.admin.entity.search.business.OutShipmentSearch;
import com.demo2do.fms.admin.handler.support.FmsJdbcSupport;
import com.demo2do.fms.admin.service.business.OutShipmentService;
import com.demo2do.fms.entity.authority.Organization;
import com.demo2do.fms.entity.business.Finance;
import com.demo2do.fms.entity.business.Shipment;
import com.demo2do.fms.entity.business.balance.Order;
import com.demo2do.fms.entity.business.balance.RevenueLittle;
import com.demo2do.fms.entity.business.balance.SapRevenueDetail;
import com.demo2do.fms.entity.business.out.Consignor;
import com.demo2do.fms.entity.business.shipment.ShipmentLine;
import com.demo2do.fms.entity.business.shipment.TransType;
import com.demo2do.fms.entity.data.business.Client;
import com.demo2do.fms.entity.data.business.Warehouse;
import com.demo2do.fms.entity.data.car.CarCategory;

/**
 *
 * @author Ark
 *
 */
@Service("outShipmentService")
public class OutShipmentServiceImpl implements OutShipmentService {
    
    private static final Logger logger = LoggerFactory.getLogger(OutShipmentServiceImpl.class);
    
    @Autowired
    private GenericDaoSupport genericDaoSupport;
    
    @Autowired
    private FmsJdbcSupport fmsJdbcSupport;
    
    @Autowired
    private RemoteMessageDaoSupport remoteMessageDaoSupport;
    
    @Autowired
    private SequenceDaoSupport sequenceDaoSupport;

    @Dao
    private OrganizationDao organizationDao;
    
    @Dao
    private OutshipmentDao outShipmentDao;
    
    @Dao
    private FinanceDao financeDao;
    
    @Dao
    private ShipmentDao shipmentDao;

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.OutShipmentService#count(com.demo2do.fms.admin.entity.search.business.OutShipmentSearch)
     */
    public int count(OutShipmentSearch outShipmentSearch) {
        
        // 1. get all the system organization
        List<Long> outSystemOrgIds = organizationDao.listOutSystemOrgIds();
        
        // 2. bulid sentence
        outShipmentSearch.addTransCorpId(outSystemOrgIds);

        // 3. do query
        return genericDaoSupport.count(outShipmentSearch.buildSentenceTemplate(), outShipmentSearch.buildParameters());
        
    }

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.OutShipmentService#search(com.demo2do.fms.admin.entity.search.business.OutShipmentSearch, com.demo2do.core.entity.page.Page)
     */
    @SuppressWarnings("unchecked")
    public List<ShipmentLine> search(OutShipmentSearch outShipmentSearch, Page page) {
        
        // 1. get all the system organization
        List<Long> outSystemOrgIds = organizationDao.listOutSystemOrgIds();
        
        // 2. bulid sentence
        outShipmentSearch.addTransCorpId(outSystemOrgIds);
        
        // 3. list shipment_line
        List<ShipmentLine> shipments = genericDaoSupport.searchForList(outShipmentSearch.buildSentenceTemplate(), outShipmentSearch.buildParameters(), page.getBeginIndex(), page.getEveryPage());
        page.calculate();
        return shipments;
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.OutShipmentService#generateDispatch(java.util.List, java.lang.String)
     */
    public void generate(List<String> loadNumbers, String operator) {
       
        if(CollectionUtils.isNotEmpty(loadNumbers)) {
            
            Date now = new Date();
            
            // 1. update dispatch status of shipment
            outShipmentDao.generate(loadNumbers, operator, now);
            
            // 2. 写调度指令已派车RemoteMessage至上游表
            List<LoadComponent> loadComponents = shipmentDao.listByLoadNumbers(loadNumbers);
            for (LoadComponent loadComponent : loadComponents) {
                remoteMessageDaoSupport.generateDispatch(loadComponent.getLoadId(), 0L, operator, loadComponent.getLoadNumber());
            }
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.OutShipmentService#cancelDispatch(java.util.List, java.lang.String)
     */
    public void cancel(List<String> loadNumbers, String operator) {
        
        if(CollectionUtils.isNotEmpty(loadNumbers)) {
            
            // 1. update dispatch info of shipment
            outShipmentDao.cancel(loadNumbers, operator, new Date());
            
            // 2. 写调度指令取消派车RemoteMessage到上游表
            List<LoadComponent> loadComponents = shipmentDao.listByLoadNumbers(loadNumbers);
            for (LoadComponent loadComponent : loadComponents) {
                remoteMessageDaoSupport.cancelDispatch(loadComponent.getLoadId(), 0L, operator, loadComponent.getLoadNumber());
            }
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.OutShipmentService#countConsignorOrder(com.demo2do.fms.admin.entity.search.business.ConsignorOrderSearch)
     */
    public int countConsignorOrder(ConsignorShipmentSearch consignorShipmentSearch) {
        String sentence = consignorShipmentSearch.buildCountTemplate();
        Map<String, Object> parameters = consignorShipmentSearch.buildParameters();
        return fmsJdbcSupport.queryForInt(sentence, parameters);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.OutShipmentService#listConsignorOrder(com.demo2do.fms.admin.entity.search.business.ConsignorShipmentSearch, com.demo2do.core.entity.page.Page)
     */
    public List<Map<String, Object>> listConsignorOrder(ConsignorShipmentSearch search, Page page) {
        
        // 1. calculate page
        Integer beginIndex = page.getBeginIndex() + 1;
        search.setBeginIndex(beginIndex);
        
        Integer endIndex = page.getCurrentPage() * page.getEveryPage();
        search.setEndIndex(endIndex);
        
        // 2. build parameters
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 3. do query
        List<Map<String, Object>> result = fmsJdbcSupport.queryForList(sentence, parameters, search.buildRowMapper());
        page.calculate();
        return result;
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.OutShipmentService#disposeOrder(java.util.List, java.lang.String)
     */
    public void dispose(List<Long> financeIds, String operator) {
        
        // 0. 准备 finance 
        List<Finance> finances = financeDao.listByFinanceIds(financeIds);
        Date now = new Date();
        
        //=========== 销单逻辑 =============
        this.accomplishConfirm(finances, financeIds, operator, now);
        
        //=========== 回单逻辑 (仅对系统外公司作为承运方有效) =============
        List<Shipment> shipments = new ArrayList<Shipment>();  // 准备 childShipment
        Set<String> loadNumbers = new LinkedHashSet<String>();  // 准备需要进行完成状态逻辑判定的 loadNumber
        
        // 2. 对于系统外公司的调度， 进行回单确认
        for (Finance finance : finances) {
            
            // 2.1 构造childShipment列表
            Shipment shipment = shipmentDao.load(finance.getShipmentId(), finance.getLoadNumber());
            shipments.add(shipment);
            
            Organization organization = genericDaoSupport.load(Organization.class, finance.getCompanyId());
            if(!organization.getSystem()) {  // 若承运方是外部公司，发送回单确认到上游系统
                
                loadNumbers.add(shipment.getLoadId() + "|" + shipment.getLoadNumber() + "|" + finance.getCompanyId());
                
                // 写回单确认RemoteMessage给上游表
                remoteMessageDaoSupport.accomplishConfirm(shipment.getShipmentId(), shipment.getLoadId(), shipment.getOperator(), shipment.getLoadNumber());
                
            }
            
        }
        
        //=========== 运输完成逻辑判定 (仅对系统外公司作为承运方有效) =============
        this.accomplishTransport(loadNumbers, operator, now);
        
        // 4. 发送暂估收入
        this.createTempRevenue(finances, shipments);
        
    }
    
    /**
     * 销单逻辑
     * 
     * @param finances
     * @param financeIds
     * @param operator
     * @param confirmTime
     */
    public void accomplishConfirm(List<Finance> finances, List<Long> financeIds, String operator, Date confirmTime) {
        
        // 1. 完成销单逻辑
        for (Finance finance : finances) {
            
            // 1.1 检查重复销单
            if (finance.getBalanced()) {
                throw new BusinessException("重复销单！委托方销单失败！");
            }
            
            // 1.2 写 RemoteMessage(type=3)表示委托方销单
            remoteMessageDaoSupport.dispose(finance.getShipmentFinanceId(), operator);
            
        }
        
        // 1.3 针对每个Finance设置销单状态为已完成
        outShipmentDao.dispose(financeIds, operator, confirmTime);
        
    }
    
    /**
     * 运输完成逻辑(如果运输已全部完成，更新调度指令为已回单状态)
     * 
     * @param loadNumbers
     * @param operator
     * @param now
     */
    public void accomplishTransport(Set<String> loadNumbers, String operator, Date now) {
        
        for(String loadString : loadNumbers) {  // 这里的 loadnumber 来自于之前的循环收集
            
            // 3.1 拆分 loadId 和 loadNumber
            String[] load = StringUtils.split(loadString, "|");
            String loadId = load[0];
            String loadNumber = load[1];
            Long companyId = Long.valueOf(load[2]);
            
            // 3.2 确定未销单的Finance数量
            int count = outShipmentDao.countBalancedStatus(loadNumber, companyId);
            
            if(count == 0) { // 3.3 如果全部销单完成
                
                // 更新调度指令的完成状态 
                outShipmentDao.accomplish(loadNumber, operator, now);
                
                // 并发送调度指令完成RemoteMessage给上游表
                remoteMessageDaoSupport.complateDispatch(Long.valueOf(loadId), 0L, operator, loadNumber);
                
            }
            
        }
        
    }
    
    /**
     * 创建暂估收入
     * 
     * @param childFinances
     * @param childShipments
     */
    private void createTempRevenue(List<Finance> childFinances, List<Shipment> childShipments){
        
        String sessionId = this.getSessionId();
        
        for(int i = 0; i < childFinances.size(); i++) {
            
            Finance childFinance = childFinances.get(i);
            Shipment childShipment = childShipments.get(i);
            
            this.createTempRevenueDetail(childFinance, childShipment, sessionId);
            
        }
                
    }

    /**
     * @param childFinance
     * @param childShipment
     */
    private void createTempRevenueDetail(Finance childFinance, Shipment childShipment, String sessionId) {
        
        SapRevenueDetail sapRevenueDetail = new SapRevenueDetail();
        
        // 0. get parent_finance and parent_shipment
        Finance parentFinance = financeDao.load(childFinance.getParentId());
        if (parentFinance == null) {
            logger.error("#createTempRevenueDetail() - finance[{}]缺失父Finance", childFinance.getShipmentFinanceId());
            throw BusinessException.newInstance("500", "子收入（" + childFinance.getShipmentFinanceId() + "）缺失对应的父收入（" + childFinance.getParentId() + "），请联系上游系统（OTMS/TMS）处理。");
        }
        
        Long parentFinanceId = parentFinance.getId();
        Long parentShipmentId = shipmentDao.getShipmentIdByFinanceId(parentFinanceId);
        if (parentShipmentId == null) {
            logger.error("#dispose() - 父Finance（shipmentFinanceId={}，id={}）对应Shipment不存在", parentFinance.getShipmentFinanceId(), parentFinanceId);
            throw BusinessException.newInstance("500", "上游Finance（" + parentFinance.getShipmentFinanceId() + "）对应的Shipment不存在，请联系上游系统（OTMS/TMS）处理。");
        }
        
        Shipment parentShipment = genericDaoSupport.get(Shipment.class, parentShipmentId);
        if (parentShipment == null) {
            logger.error("#dispose() - 父Finance（shipmentFinanceId={}）对应Shipment不存在", parentFinance.getShipmentFinanceId());
            throw BusinessException.newInstance("500", "上游Finance（" + parentFinance.getShipmentFinanceId() + "）对应的Shipment不存在，请联系上游系统（OTMS/TMS）处理。");
        }
        
        // 1 init revenue_detail by revenue_little and order
        RevenueLittle revenueLittle = genericDaoSupport.get(RevenueLittle.class, parentFinanceId);
        Long smOid = revenueLittle.getSmOid();
        Order order = ( smOid != null ? genericDaoSupport.load(Order.class, smOid) : null);
        sapRevenueDetail.init(revenueLittle, order);
        
        // edit session id
        sapRevenueDetail.editSessionId(sessionId);
        
        // 2 edit basic states、 financeId
        sapRevenueDetail.editBasic("N", parentFinance.getShipmentFinanceId());
        
        // 3 公司代码
        Organization parentOrganization = genericDaoSupport.load(Organization.class, parentFinance.getCompanyId());
        sapRevenueDetail.editOrganization(parentOrganization.getCode());
        
        // 4 制造商
        childShipment.getId();
        CarCategory carCategory = genericDaoSupport.load(CarCategory.class, childShipment.getVhCartypeId());
        String ayzzdm = carCategory.getManufactorCode();
        sapRevenueDetail.editManufactor(ayzzdm);
        
        // 5 客户代码
        Long consignorId = parentFinance.getConsignorId();
        Client client = null;
        if(consignorId != null){
            
            //load client by customer_id
            client = genericDaoSupport.load(Client.class, parentShipment.getCustomerId());
            
            Consignor consignor = genericDaoSupport.load(Consignor.class, consignorId);
            sapRevenueDetail.editClient(consignor.getCode());
            
        } else {
            //load client by customer_id
            client = genericDaoSupport.load(Client.class, parentFinance.getCustomerId());
            sapRevenueDetail.editClient(client.getCode());
        }
        
        // 业务内外标识、订单建立日期
        if (client.getInternal()) {
            sapRevenueDetail.editBusinessIndentify("X");
        } else {
            sapRevenueDetail.editBusinessIndentify("");
            sapRevenueDetail.editOrderCreateDate(DateFormatUtils.format(parentShipment.getShipmentDate(), "yyyyMMdd"));
        }
        
        // 6 暂估标识
        String zmzgbz = parentFinance.getTemporary() ? "X" : ""; 
        sapRevenueDetail.editTemporary(zmzgbz);
        
        // 7. 运输方式
        TransType transType = childShipment.getTransType();
        sapRevenueDetail.editTransType(transType.ordinalString(), transType.getDescription());
        
        // 8. 回单标识、回单日期
        sapRevenueDetail.editAccomplishConfirm("X");
        
        // 9. 起始地、目的地信息
        Warehouse srcWh = genericDaoSupport.load(Warehouse.class, parentFinance.getSrcWhId());
        Warehouse destWh = genericDaoSupport.load(Warehouse.class, parentFinance.getDestWhId());
        sapRevenueDetail.editLocation(srcWh, destWh);
        
        // 10 运输收入、运输单价等收入信息
        sapRevenueDetail.editFinance(parentFinance);
        
        // 11 商品车vin码
        String ayvin = childShipment.getFirstShipmentId().toString();
        String aysvin = childShipment.getVinCode();
        sapRevenueDetail.editVinCode(StringUtils.isBlank(ayvin) ? "0" : ayvin, aysvin);
        
        // 12 调度单号
        if (StringUtils.isBlank(parentFinance.getLoadNumber())) {
            sapRevenueDetail.editShipmentOrder(this.getAyhddd(parentShipment));
        } else {
            sapRevenueDetail.editShipmentOrder(parentFinance.getLoadNumber());
        }
        
        // 13. 订单创建日期
        if(parentFinance.getCreateDate() != null){
            String ayddrq = DateFormatUtils.format(childShipment.getShipmentDate(), "yyyyMMdd");
            sapRevenueDetail.editOrderCreateDate(ayddrq);
        }
        
        // 15. order code
        Order chilidOrder = genericDaoSupport.load(Order.class, childShipment.getOrderId());
        sapRevenueDetail.editOrderCode(chilidOrder.getCode());
        
        // 16. 批量更新标识、最后修改日期、合并目标调度单号、传入财务日期等
        String aysrdh = childShipment.getOrderId().toString();
        sapRevenueDetail.editSapField(aysrdh, "I", "N", chilidOrder.getCode(), "", "");

        genericDaoSupport.save(sapRevenueDetail);
        
    }
    
    /**
     * @return
     */
    private String getSessionId() {
        
        try {
            
            String sessionId = DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS");
            
            // 1. get sequence by sequenceSupport
            String sequence = sequenceDaoSupport.getSapSequence("SAP_CODE");
            Long code = Long.valueOf(sequence);
            
            // 2. construct sequence
            if (code < 10) {
                sessionId = sessionId + "00" + sequence;
            } else if (code < 100) {
                sessionId = sessionId + "0" + sequence;
            } else {
                sessionId = sessionId + sequence;
            }
            
            return sessionId;
            
        } catch (Exception e) {
            logger.error("#getSessionId() - get session id fail, exception is [{}]", e);
            throw BusinessException.newInstance("500", "获取sessionId失败!委托方销单失败!");
        }
        
    }
    
    /**
     * 获取调度单号
     * 
     * @param parentShipment
     * @return
     */
    private String getAyhddd(Shipment parentShipment){
        
        if (parentShipment.getFirstShipmentId() != parentShipment.getId()){
            Shipment firstShipment = shipmentDao.loadByShipmentId(parentShipment.getFirstShipmentId());
            return firstShipment != null ? firstShipment.getLoadNumber() : null;
        }
        
        return null;
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.OutShipmentService#exportAll(com.demo2do.core.persistence.support.QueryBuilder, org.springframework.jdbc.core.RowMapper)
     */
    public List<Map<String, Object>> exportAll(QueryBuilder queryBuilder, RowMapper<Map<String, Object>> rowMapper) {
        String sentence = queryBuilder.buildSentenceTemplate();
        Map<String, Object> parameters = queryBuilder.buildParameters();
        return fmsJdbcSupport.queryForList(sentence, parameters, rowMapper);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.OutShipmentService#search(com.demo2do.fms.admin.entity.report.business.ConsignorShipmentReportSearch, com.demo2do.core.entity.page.Page)
     */
    public List<Map<String, Object>> search(ConsignorShipmentReportSearch search, Page page) {
        
        // 1. calculate page
        Integer beginIndex = page.getBeginIndex() + 1;
        search.setBeginIndex(beginIndex);
        
        Integer endIndex = page.getCurrentPage() * page.getEveryPage();
        search.setEndIndex(endIndex);
        
        // 2. build parameters
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 3. do query
        List<Map<String, Object>> result = fmsJdbcSupport.queryForList(sentence, parameters, search.buildRowMapper());
        page.calculate();
        return result;
        
    }
    
}
